JavaScript 程式執行原理:JS中的物件導向


Posted by backas36 on 2021-08-16

在 ES6 以前,還沒有 class 可以來創建一個類別,類別可以想像成定義一個 object 的設計稿,我們可以透過 class 來描述一個 object 長什麼樣子,通常這個 object 我們稱為 instance,例如你創建了一個手機的設計稿,設計稿裡面有描述手機該叫什麼名字,有什麼功能可以使用,而當我們去規範這個一支手機叫 'iPhone',並且有打電話這功能,那這支手機我們稱為 instance。

即使在 ES6 以前沒有 class 可以使用,我們仍然 function 的方式去實現物件導向的概念,或許之前我們已經常使用類似的概念的,可能我們沒有發現而已。

以下是簡單的例子:

function Phone(name){
    var myName = name
    return {
        getName: function(){
            return myName
        },
        calling: function() {
            console.log(myName + ' => call someone...')
        }
    }
}

var iPhone = Phone('iPhone')
iPhone.calling()

var s3 = Phone('S3')
s3.calling()

我們創建了兩支手機,iPhone 和 S3,並且他們都有打calling的 method 可以使用。所以理論上 iPhone.calling === s3.calling 應該要是 true 才對,但其實會回傳 false,由此可知當我們創建了一萬支手機,我們就會創建了一萬次 calling 這 method,這不符合邏輯,因為所有手機應該都要共用 calling。

所以我們改寫了一下程式:

function Phone(name){
    return this.name = name
}

Phone.prototype.getName = function(){
    return this.name
}

Phone.prototype.calling = function(){
    console.log(this.name + ' => call someone...')
}

var iPhone = new Phone('iPhone')
iPhone.calling()

var s3 = new Phone('S3')
s3.calling()

注意到我們使用了關鍵字 new 來新增一個 instance,還有利用 prototype 來創建共用的 method,並且現在 iPhone.calling === s3.calling 就是 true 了。

其實每當我們新增一個實例的時候 JS 會幫我們建立 __proto__,而這個 __proto__ 就是 Phone 的 prototype

iPhone.__proto__ === Phone.prototype //true

當我們輸入 iPhone.calling() 的時候會發生一些事:

  • iPhone 本身有沒有 calling
  • iPhone.proto (Phone.prototype) 有沒有 calling
  • iPhone.proto.proto (Object.prototye) 有沒有 calling

是一層一層往上找的概念,這就是 prototype chain 原型鍊,那麼如果真的連 Object.prototype 也找不到呢,就會找到最頂層,回傳 null,那麼 iPhone.calling() 就會回傳錯誤。

由此可知,其實我們可以在 Object 上加上 prototype ,這樣 iPhone 一樣也可以使用 calling。

其實我們很常使用一些 method,像是 Array 的 push, join, .....這些,就是利用 prototype 的特性,當我們建立一個 array 的時候這個 array 的 proto 會與 JS中 Array.prototype 連接起來,所以我們才可以使用 Array.prototype 的 method。(正確來說是我們新增的 array 會繼承了 Array.prototype 的所有屬性和方法)

let arr = []
console.log(arr.__proto__ === Array.prototype) // true
console.log(arr.__proto__) // 會列出所有 Array 可以使用的 method

那麼 __proto__ 是怎麼來的呢,其實是因為 new 這個關鍵字在背後幫我們做了幾件事情,你可以把它想成這是以下這樣:

function Phone(name){
    return this.name = name
}

Phone.prototype.getName = function(){
    return this.name
}

Phone.prototype.calling = function(){
    console.log(this.name + ' => call someone...')
}

// new 的工作在這裡
function newPhone(name) {
    var obj = {}
    Phone.call(obj, name)
    obj.__proto__ = Phone.prototype
    return obj
}

// 這樣就可以不用 new 了
var oppo = newPhone('oppo')
oppo.calling()

new 的主要工作就是生成一個 Object,然後重點在將這個 Object 的 proto與 Phone 的 prototype 接起來,然後再回傳這個 Object,我們就可以使用 calling 了。

以上都是介紹 ES5 實現類別與實例的方式,那到了 ES6 之後,我們就有了 class 來取代這個 function ,但其實背後做的事情是差不多的。

class Phone{

    constructor(name) {
        this.name = name
    }

    getName(name) {
        return this.name
    }

    calling() {
        console.log(this.name + ' => call someone')
    }
}

var iPhone = new Phone('iPhone')
iPhone.calling()

注意這個 constructor,是使用了 class 自動幫我們創建的,裡面會放著我們對該實例的描述。

最後,在物件導向中有個很重要的觀念就是繼承,extends

假如我們現在新增一支手機,一樣有 calling 功能,也跟剛剛新增的手機一樣有名字,但這支手機可以照相其他手機不行,我們就可以使用繼承的概念實現,因為只是多了一個其他手機沒有的功能而已,剩下的都共用。

class Phone{

    constructor(name) {
        this.name = name
    }

    getName(name) {
        return this.name
    }

    calling() {
        console.log(this.name + ' => call someone')
    }
}

class PhoneM extends Phone {
    constructor(name) {
        super(name)
    }
    takePhoto(){
        console.log(this.name + ' => taking photo') 
    }
}


var iPhoneM = new PhoneM('iPhoneM')
iPhoneM.calling()

其實生活上很多例子都跟物件導向有相關,例如有個類別是 user,那就可以創建很多個 user 出來,每個 user 有不一樣的 id 但是都有著一樣的 method 可以使用,例如登入、新增文章....之類的,那我們可以使用繼承的觀念去新增一個 user 是 admin , 因為 admin 其實很多 method 和屬性都跟 user 共用,可能只是多加一個可以刪除文章的功能,當然生活上其實很多東西可以想成是類別與實例來看!這樣或許就不會那麼難理解了。


在以上我們大概知道了物件導向是什麼東西,也用了 JS 去簡單的示範怎麼呈現類別與實例,接下來我將這些片段整理一下,首先我們要知道設計物件導向的類別的時候幾個大觀念:

  • 抽象化 Abstraction:簡單的說,就是在抽象化概念中我們不需要去談細節,就像你在使用 setTimeout 或 監聽事件的時候,我們並不需要去知道運作的細節,我只要知道怎麼去使用,所以在設計類別的時候我們要站在使用者的角度,我只要讓使用者知道怎麼使用就好。
  • 封裝 Encapsulation:我們應該要明確定的定義哪些屬性是可以對外開放,哪些是不希望可以從外部被操控。
  • 繼承 Inheritance:就像剛剛舉的 user 與 admin 的例子,當我們建立 user 和 admin 類別時,實際上 admin 也是其中一個 user,所以 admin 應該要繼承 user 可以使用的屬性,並且 admin 有著自己獨有的屬性,總之,user 是 admin 的 parent 。
  • 多型 Polymrophism:可以想像成多型就是指一個類別可以再延伸出多個子類別,而此這些子類別有許多跟父類別共用的屬性,跟父類別有點相似但其實有一些不一樣。(其實這部分我還沒有到非常懂啦,先解說到這邊)

好了,現在對 JS 的物件導向有了基本認識之後,我們可以來認識 this 這個大魔王了


#js #物件導向







Related Posts

Command Line Interface (CLI) 超入門

Command Line Interface (CLI) 超入門

部署 (1) —— 建立 AWS EC2 主機及 SSH 連線

部署 (1) —— 建立 AWS EC2 主機及 SSH 連線

CSS:object-fit:none的表現

CSS:object-fit:none的表現


Comments